/****************************************************************************

 Copyright (C) 2009 Jean Pierre Charalambos. All rights reserved.

 Jean Pierre Charalambos (http://dis.unal.edu.co/profesores/pierre/) is an
 Affiliate Associate Professor of the Faculty of Engineering at National
 University of Colombia (http://www.unal.edu.co/).

 This file is part of maku version 0.1.

 http://code.google.com/p/maku/
 email: jpcharalambosh@unal.edu.co

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the COPYING file included in the packaging of this file.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#include <QtGui>

#include "auxviewer.h"
#include "viewerdock.h"
#include "mainwindow.h"

using namespace std;

MainWindow::MainWindow(QString fileName, QWidget *parent) : QMainWindow(parent) {
  ui.setupUi(this);
  QMetaObject::connectSlotsByName(this);
  connect(ui.openAction, SIGNAL(triggered()), this,SLOT(open()));
  connect(ui.saveAction, SIGNAL(triggered()), this,SLOT(save()));
  connect(ui.saveAsAction, SIGNAL(triggered()), this,SLOT(saveAs()));
  connect(ui.closeAction, SIGNAL(triggered()), this,SLOT(closeModel()));
  connect(ui.exitAction, SIGNAL(triggered()), this,SLOT(exitApp()));
  connect(ui.drawCameraAuxAction, SIGNAL(toggled(bool)), this,SLOT(drawCameraAux(bool)));
  connect(ui.refreshAction, SIGNAL(triggered()), this,SLOT(refresh()));
  connect(ui.aboutAction, SIGNAL(triggered()), this,SLOT(about()));
  setAttribute(Qt::WA_DeleteOnClose);
  isUntitled = true;
  createWidgets(fileName.isEmpty());
  importActions();
  statusBar()->showMessage(tr("Ready"));

  readSettings();

  setWindowTitle(tr("Maku-3D"));

  if (!fileName.isEmpty())
    openModel(fileName);
}

MainWindow::~MainWindow() {
  delete scene;
  //seems that Qt::WA_DeleteOnClose does the following for us:
  //delete viewer;
  //delete top;
  //delete front;
  //delete side;
}

void MainWindow::createWidgets(bool loadCube) {
  scene = new Scene();
  connect(scene, SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
  //dockwidgets:
  topDockWidget = new ViewerDock(tr("top"), this);
  sideDockWidget = new ViewerDock(tr("side"), this);
  frontDockWidget = new ViewerDock(tr("front"), this);

  if (QGLFormat::hasOpenGL()) {
    QGLFormat format;
    format.setDepth(true);
    //format.setStencil(false);// not for now...
    format.setDoubleBuffer(true);
    //format.setSampleBuffers(true); //set it for antialiasing

    QGLFormat::setDefaultFormat(format);
  }

  viewer = new Viewer(scene, this);

  top   = new AuxViewer(scene, topDockWidget, 1, viewer);
  connect(ui.constraintRotationAction, SIGNAL(toggled(bool)), top, SLOT(constraintRotation(bool)));
  side   = new AuxViewer(scene, sideDockWidget, 0, viewer);
  connect(ui.constraintRotationAction, SIGNAL(toggled(bool)), side, SLOT(constraintRotation(bool)));
  front  = new AuxViewer(scene, frontDockWidget, 2, viewer);
  connect(ui.constraintRotationAction, SIGNAL(toggled(bool)), front, SLOT(constraintRotation(bool)));

  #ifdef WITH_GLEW
   initGLEW();
  #endif

  //layout
  QGridLayout * gridLayout = new QGridLayout(viewer);
  gridLayout->setSpacing(0);
  gridLayout->setMargin(0);
  QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
  sizePolicy.setHorizontalStretch(0);
  sizePolicy.setVerticalStretch(0);
  sizePolicy.setHeightForWidth(viewer->sizePolicy().hasHeightForWidth());
  viewer->setSizePolicy(sizePolicy);
  viewer->setMinimumSize(QSize(640, 480));
  //viewer->setMaximumSize(640, 480);
  //end layout

  setCentralWidget(viewer);

  // Create the aux viewers
  //top
  topDockWidget->setWidget(top);
  top->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Expanding );
  connect(topDockWidget->toggleViewAction(), SIGNAL(toggled(bool)), top, SLOT(slotDockVisible(bool)));
  addDockWidget(Qt::TopDockWidgetArea, topDockWidget);

  //side
  sideDockWidget->setWidget(side);
  side->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Expanding );
  connect(sideDockWidget->toggleViewAction(), SIGNAL(toggled(bool)), side, SLOT(slotDockVisible(bool)));
  addDockWidget(Qt::TopDockWidgetArea, sideDockWidget);

  //front
  frontDockWidget->setWidget(front);
  front->setSizePolicy ( QSizePolicy::Expanding, QSizePolicy::Expanding );
  connect(frontDockWidget->toggleViewAction(), SIGNAL(toggled(bool)), front, SLOT(slotDockVisible(bool)));
  addDockWidget(Qt::RightDockWidgetArea, frontDockWidget);

  topDockWidget->setVisible (false);
  frontDockWidget->setVisible (false);
  sideDockWidget->setVisible (false);

  if (loadCube)
    scene->loadCube();
}

#ifdef WITH_GLEW    
void MainWindow::initGLEW() {
  viewer->makeCurrent();
  GLenum err = glewInit();
  if (GLEW_OK != err) {
    cout<<"Couldn't init GLEW. Please check your installation!"<<endl;
    exit(1);
  }
  if (glewIsSupported("GL_VERSION_2_0")) {
    shaderComboBox = new QComboBox( ui.sceneToolBar );
    cout<<"OpenGL 2.0 is supported!"<<endl;
    QStringList list;
    list << "fixed functionality" << "Cook-Torrance" << "envmap" << "glass" << "Hatch" << "minnaert" << "phong" << "SEM"
         << "toon" << "dimple" << "first" << "gooch" << "lattice" << "Oren-Nayar" <<"polkadot3d" <<"stripes2"<<"xray";
    shaderComboBox->insertItems(0, list);
    ui.sceneToolBar->addWidget(shaderComboBox);
    scene->hasOpenGL2 = true;
    //shaders:
    connect(shaderComboBox, SIGNAL(currentIndexChanged(const QString &)), viewer, SLOT(setShader(const QString &)));
    connect(shaderComboBox, SIGNAL(currentIndexChanged(const QString &)), top, SLOT(setShader(const QString &)));
    connect(shaderComboBox, SIGNAL(currentIndexChanged(const QString &)), side, SLOT(setShader(const QString &)));
    connect(shaderComboBox, SIGNAL(currentIndexChanged(const QString &)), front, SLOT(setShader(const QString &)));
  }
  else {
    cout<<"OpenGL 2.0 is not supported!"<<endl;
    scene->hasOpenGL2 = false;
  }
  viewer->doneCurrent();
}
#endif

void MainWindow::importActions() {
  ui.sceneToolBar->addAction(scene->pointColorDialogAct);

  ui.sceneMenu->addActions(scene->boundingGroupActs->actions());
  ui.sceneMenu->addSeparator();
  ui.sceneMenu->addAction(scene->openTexAct);
  ui.sceneMenu->addAction(scene->detachTexAct);
  ui.sceneMenu->addAction(scene->decimateAct);
  ui.sceneMenu->addSeparator();
  ui.sceneMenu->addAction(scene->pointColorDialogAct);
  ui.sceneMenu->addSeparator();
  ui.sceneMenu->addActions(scene->drawModesActs->actions());
  //menus:
  ui.dockWidgetMenu->addMenu(topDockWidget->menu);
  ui.dockWidgetMenu->addMenu(sideDockWidget->menu);
  ui.dockWidgetMenu->addMenu(frontDockWidget->menu);
}

//events:
void MainWindow::keyPressEvent(QKeyEvent *e) {
  if(ui.constraintRotationAction->isChecked())
    return;
  const Qt::KeyboardModifiers modifiers = e->modifiers();
  if ( (e->key()==Qt::Key_1) && (modifiers==Qt::NoButton) ) {
    cout <<"set camera in view 1"<< endl;
    top->setCameraAsInViewer();
  }
  else
    if ( (e->key()==Qt::Key_2) && (modifiers==Qt::NoButton) ) {
      cout <<"set camera in view 2"<< endl;
      side->setCameraAsInViewer();
    }
    else
      if ( (e->key()==Qt::Key_3) && (modifiers==Qt::NoButton) ) {
        cout <<"set camera in view 3"<< endl;
        front->setCameraAsInViewer();
      }
}

void MainWindow::closeEvent(QCloseEvent *event) {
  if (maybeSave()) {
    writeSettings();
    event->accept();
  }
  else {
    event->ignore();
  }
}

//Actions:

bool MainWindow::open() {
  if (isWindowModified()) {
    QMessageBox::StandardButton ret;
    ret = QMessageBox::warning(this, tr("Scene"),
          tr("The scene has been modified.\n"
             "Do you want to save your changes before?"),
          QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    if (ret == QMessageBox::Save)
      save(); //properly calls setWindowModified and ui.saveAction->setEnabled
    else if(ret == QMessageBox::Discard) {
      setWindowModified(false);
      ui.saveAction->setEnabled(false);
    }
    else if (ret == QMessageBox::Cancel)
      return false;
  }
  return openModel();
}

bool MainWindow::openModel(QString fileName) {
  bool open=false;
  if( ! fileName.isNull() ) {
    open=scene->openModel(fileName);
    //viewer->updateGL();//no hace falta
    setCurrentFile(fileName);
    statusBar()->showMessage(tr("File loaded"), 2000);
  }
  if (open) {
    ui.closeAction->setEnabled(true);
    ui.saveAsAction->setEnabled(true);
  }
  return open;
}

bool MainWindow::save() {
  if (isUntitled) {
    return saveAs();
  }
  else {
    return saveFile(currentFile);
  }
}

bool MainWindow::saveAs() {
  QString * selectedFilter = new QString("*.obj");
  QString fileName = QFileDialog::getSaveFileName(this, "Save scene as", currentFile, "Models (*.obj *off *.om *.ply *.stl *.stla)", selectedFilter);
  if (fileName.isEmpty())
    return false;
  return saveFile(fileName);
}

bool MainWindow::closeModel() {
  if (isWindowModified()) {
    QMessageBox::StandardButton ret;
    ret = QMessageBox::warning(this, tr("Scene"),
          tr("The scene has been modified.\n"
             "Do you want to save your changes before?"),
          QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    if (ret == QMessageBox::Save)
      save(); //properly calls setWindowModified and ui.saveAction->setEnabled
    else if(ret == QMessageBox::Discard) {
      setWindowModified(false);
      ui.saveAction->setEnabled(false);
    }
    else if (ret == QMessageBox::Cancel)
      return false;
  }
  setWindowModified(false);
  ui.saveAction->setEnabled(false);
  setCurrentFile();
  return scene->loadCube();
}

void MainWindow::refresh() {
  if(top) top->updateGL();
  if(side) side->updateGL();
  if(front) front->updateGL();
}

void MainWindow::drawCameraAux(bool c) {
  if(top) top->setDrawMainCamera(c);
  if(side) side->setDrawMainCamera(c);
  if(front) front->setDrawMainCamera(c);
}

void MainWindow::about(){
  QMessageBox::about(this, tr("About Application"),
                     tr("An experimental <b>Viewer</b> that supports "
                        "advanced rendering techniques."));
}

//misc:

void MainWindow::documentWasModified() {
  setWindowModified(true);
  ui.saveAction->setEnabled(true);
}

bool MainWindow::saveFile(const QString &fileName) {
  QFile file(fileName);
  if (!file.open(QFile::WriteOnly | QFile::Text)) {
    QMessageBox::warning(this, tr("SDI"),
                         tr("Cannot write file %1:\n%2.")
                         .arg(fileName)
                         .arg(file.errorString()));
    return false;
  }

  QApplication::setOverrideCursor(Qt::WaitCursor);
  bool result = scene->saveModel(fileName);
  QApplication::restoreOverrideCursor();

  if(!result)
    return false;

  setCurrentFile(fileName);
  statusBar()->showMessage(tr("File saved"), 2000);

  ui.saveAction->setEnabled(false);
  return true;
}

void MainWindow::readSettings() {
    QSettings settings(tr("Trolltech"), tr("Application Example"));
    QPoint pos = settings.value(tr("pos"), QPoint(200, 200)).toPoint();
    QSize size = settings.value(tr("size"), QSize(400, 400)).toSize();
    resize(size);
    move(pos);
}

void MainWindow::writeSettings() {
    viewer->saveStateToFile();
    QSettings settings(tr("Trolltech"), tr("Application Example"));
    settings.setValue(tr("pos"), pos());
    settings.setValue(tr("size"), size());
}

bool MainWindow::maybeSave() {
  if (isWindowModified()) {
    QMessageBox::StandardButton ret;
    ret = QMessageBox::warning(this, tr("Scene"),
          tr("The scene has been modified.\n"
             "Do you want to save your changes?"),
          QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
    if (ret == QMessageBox::Save)
      return save();
    else if (ret == QMessageBox::Cancel)
      return false;
  }
  return true;
}

void MainWindow::setCurrentFile(const QString &fileName) {
  currentFile = fileName;
  isUntitled = fileName.isEmpty();
  setWindowModified(false);
  if (currentFile.isEmpty())
      setWindowTitle(tr("Maku3D"));
  else
      setWindowTitle(tr("%1[*] - %2").arg(strippedName(currentFile))
                                     .arg(tr("Maku3D")));
}

QString MainWindow::strippedName(const QString &fullFileName) {
  return QFileInfo(fullFileName).fileName();
}
